package com.alibaba.japan.club.runtime.springmvc.servlet.handler;

import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import com.alibaba.japan.club.runtime.springmvc.exception.SpringMVCException;

/**
 * 该类是SimpleMappingExceptionResolver的扩展类<br>
 * SimpleMappingExceptionResolver类支持特定异常类型跳转到指定页面的功能<br>
 * 如:<br>
 * <code> com.alibaba.japan.club.web.module.home.exception.ClubException --> /home/error </code> <br>
 * <br>
 * JapanSimpleMappingExceptionResolver类实现通过异常中的errorCode属性来实现支持指定异常页面跳转的功能<br>
 * 如:<br>
 * <code> 
 * 		com.alibaba.japan.club.web.module.home.exception.ClubException: <br> 
 * 		errorCode = "0" --> /home/error <br>
 * 		errorCode = "1" --> /home/index <br>
 * </code>
 * 
 * 
 * @author:shaochuan.wangsc
 * @date:2009-3-24
 * 
 */
public class JapanSimpleMappingExceptionResolver extends SimpleMappingExceptionResolver {

	/**
	 * mapping映射文件
	 */
	private Map<String, Map<String, String>> exceptionsMapping;

	private Properties exceptionMappings;

	private String defaultErrorView;

	public void setExceptionsMapping(Map<String, Map<String, String>> exceptionsMapping) {
		this.exceptionsMapping = exceptionsMapping;
	}

	/**
	 * Set the mappings between exception class names and error view names. The exception class name can be a substring,
	 * with no wildcard support at present. A value of "ServletException" would match
	 * <code>javax.servlet.ServletException</code> and subclasses, for example.
	 * <p>
	 * <b>NB:</b> Consider carefully how specific the pattern is, and whether to include package information (which
	 * isn't mandatory). For example, "Exception" will match nearly anything, and will probably hide other rules.
	 * "java.lang.Exception" would be correct if "Exception" was meant to define a rule for all checked exceptions. With
	 * more unusual exception names such as "BaseBusinessException" there's no need to use a FQN.
	 * <p>
	 * Follows the same matching algorithm as RuleBasedTransactionAttribute and RollbackRuleAttribute.
	 * 
	 * @param mappings exception patterns (can also be fully qualified class names) as keys, and error view names as
	 *            values
	 * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
	 * @see org.springframework.transaction.interceptor.RollbackRuleAttribute
	 */
	public void setExceptionMappings(Properties mappings) {
		this.exceptionMappings = mappings;
	}

	/**
	 * Set the name of the default error view. This view will be returned if no specific mapping was found.
	 * <p>
	 * Default is none.
	 */
	public void setDefaultErrorView(String defaultErrorView) {
		this.defaultErrorView = defaultErrorView;
	}

	/**
	 * Determine the view name for the given exception, searching the {@link #setExceptionMappings "exceptionMappings"},
	 * using the {@link #setDefaultErrorView "defaultErrorView"} as fallback.
	 * 
	 * @param ex the exception that got thrown during handler execution
	 * @param request current HTTP request (useful for obtaining metadata)
	 * @return the resolved view name, or <code>null</code> if none found
	 */
	public String determineViewName(Exception ex, HttpServletRequest request) {
		String viewName = null;
		// Check for specific exceptions mapping.
		if (this.exceptionsMapping != null) {
			viewName = findMatchingViewName(this.exceptionsMapping, ex);
		}
		// Check for specific exception mappings.
		if (viewName == null && this.exceptionMappings != null) {
			viewName = findMatchingViewName(this.exceptionMappings, ex);
		}
		// Return default error view else, if defined.
		if (viewName == null && this.defaultErrorView != null) {
			if (logger.isDebugEnabled()) {
				logger.debug("Resolving to default view '" + this.defaultErrorView + "' for exception of type ["
						+ ex.getClass().getName() + "]");
			}
			viewName = this.defaultErrorView;
		}
		return viewName;
	}

	/**
	 * 找到符合自定义exceptions mapping的错误视图
	 * 
	 * @param exceptionsMapping
	 * @param ex
	 * @return
	 */
	public String findMatchingViewName(Map<String, Map<String, String>> exceptionsMapping, Exception ex) {
		String viewName = null;
		String dominantMapping = null;
		SpringMVCException exception;
		int deepest = Integer.MAX_VALUE;

		for (Entry<String, Map<String, String>> entry : exceptionsMapping.entrySet()) {
			String key = entry.getKey();
			Map<String, String> value = entry.getValue();
			int depth = getDepth(key, ex);
			if (depth >= 0 && depth < deepest) {
				dominantMapping = key;
				if (ex instanceof SpringMVCException) {
					exception = (SpringMVCException) ex;
					viewName = value.get(exception.getErrorCode());
				}
			}
			if (viewName != null) {
				break;
			}
		}

		if (viewName != null && logger.isDebugEnabled()) {
			logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName()
					+ "], based on exception mapping [" + dominantMapping + "]");
		}
		return viewName;
	}

	/**
	 * Find a matching view name in the given exception mappings.
	 * 
	 * @param exceptionMappings mappings between exception class names and error view names
	 * @param ex the exception that got thrown during handler execution
	 * @return the view name, or <code>null</code> if none found
	 * @see #setExceptionMappings
	 */
	@SuppressWarnings("unchecked")
	public String findMatchingViewName(Properties exceptionMappings, Exception ex) {
		String viewName = null;
		String dominantMapping = null;
		int deepest = Integer.MAX_VALUE;
		for (Enumeration names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
			String exceptionMapping = (String) names.nextElement();
			int depth = getDepth(exceptionMapping, ex);
			if (depth >= 0 && depth < deepest) {
				deepest = depth;
				dominantMapping = exceptionMapping;
				viewName = exceptionMappings.getProperty(exceptionMapping);
			}
		}
		if (viewName != null && logger.isDebugEnabled()) {
			logger.debug("Resolving to view '" + viewName + "' for exception of type [" + ex.getClass().getName()
					+ "], based on exception mapping [" + dominantMapping + "]");
		}
		return viewName;
	}

}
